package com.toddsoft.toddtris.applet;

import java.awt.Graphics;
import java.awt.Image;
import java.awt.Color;
import java.awt.event.*;
import java.awt.*;

import java.applet.AudioClip;

import com.toddsoft.toddtris.model.WellListener;
import com.toddsoft.toddtris.model.Block;
import com.toddsoft.toddtris.model.Well;
import com.toddsoft.toddtris.model.HallOfFame;
import com.toddsoft.toddtris.model.HallOfFameDataProvider;
import com.toddsoft.toddtris.control.simple.SimpleController;
import com.toddsoft.toddtris.control.ToddtrisController;
import com.toddsoft.toddtris.control.RepeatController;

public class ToddtrisApplet extends java.applet.Applet 
                            implements WellListener,
                                       HallOfFameDataProvider {

    private Well well;
    private RepeatController controller;
    
    static final int X_OFFSET = 20;
    static final int Y_OFFSET = 0;

    private static final int BLOCK_WIDTH = 21;
    private static final int BLOCK_HEIGHT = 21;

    private static final int GAME_HEIGHT = 438;
    private static final int GAME_WIDTH = 439;

    /** width of just the well at the left edge of the applet */ 
    private static final int WELL_WIDTH = 250;


    private Frame frame; // The parent frame of this applet

    private HallOfFameForm hallOfFameForm;
    private StartGameForm startGameForm;

    private Image[] blockPics = new Image[7];
    private Image[] deadBlockPics = new Image[8];
    private Image backGround;

    //sound effects
    private AudioClip rotateClip;
    private AudioClip madeLineClip;
    private AudioClip landBlockClip;

    /**
     * the well, next block and lines as they appear on screen
     */
    private int screenWell[][] = new int [Well.WELL_WIDTH][Well.WELL_HEIGHT] ;
    private Block screenNextBlock;
    private int screenLines = -1;

    /* The color of the background to the HTML page this applet is on */
    private Color backgroundColour = Color.black;

    private boolean isNew = true;
    private boolean copyrightShown = false;
        
    /**
     * Loads all the images and sets up the game ready for play
     */
    public void init() {

        /*
         * Do all the logical set up of well, etc first
         */
        well = new Well(0, 0, 0);

        well.addListener(this);

        /* create a hall of fame */
        new HallOfFame(this, well);

        ToddtrisController wrappedController = new SimpleController(well);
        this.controller = new RepeatController(wrappedController);


        /*
         * Everything else is pure GUI stuff - loading sounds and images,
         * setting up key adapters, etc
         */ 
        requestFocus();

        // get the parent frame of this applet so we can pop up dialogues
        Component anchorPoint = getParent();
        while (!(anchorPoint instanceof Frame)) {
            anchorPoint = anchorPoint.getParent();
        }
        frame = (Frame)anchorPoint;

        hallOfFameForm = new HallOfFameForm(frame);
        startGameForm = new StartGameForm(frame);

        //load the sounds
        //(actually not loaded until they are first played, but...)
        rotateClip = this.getAudioClip(this.getCodeBase(), 
                                       "sounds/rotate.au");

        madeLineClip = this.getAudioClip(this.getCodeBase(), 
                                         "sounds/made_line.au");

        landBlockClip = this.getAudioClip(this.getCodeBase(), 
                                          "sounds/land_block.au");

        //load all the block images
        blockPics[0] = getImage(getCodeBase(), "images/block1.gif");
        blockPics[1] = getImage(getCodeBase(), "images/block2.gif");
        blockPics[2] = getImage(getCodeBase(), "images/block3.gif");
        blockPics[3] = getImage(getCodeBase(), "images/block4.gif");
        blockPics[4] = getImage(getCodeBase(), "images/block5.gif");
        blockPics[5] = getImage(getCodeBase(), "images/block6.gif");
        blockPics[6] = getImage(getCodeBase(), "images/block7.gif");

        deadBlockPics[0] = getImage(getCodeBase(), "images/deadblock1.gif");
        deadBlockPics[1] = getImage(getCodeBase(), "images/deadblock2.gif");
        deadBlockPics[2] = getImage(getCodeBase(), "images/deadblock3.gif");
        deadBlockPics[3] = getImage(getCodeBase(), "images/deadblock4.gif");
        deadBlockPics[4] = getImage(getCodeBase(), "images/deadblock5.gif");
        deadBlockPics[5] = getImage(getCodeBase(), "images/deadblock6.gif");
        deadBlockPics[6] = getImage(getCodeBase(), "images/deadblock7.gif");

        //generic dead block for lines added on
        deadBlockPics[7] = getImage(getCodeBase(), "images/deadblock.gif");

        //load background image
        backGround = getImage(getCodeBase(), "images/background.jpg");

        /* get the background color parameter */
        String backgroundColourStr = getParameter("backgroundcolour");

        if (backgroundColourStr != null) {
            int backgroundColourInt = Integer.parseInt(backgroundColourStr, 16);
            backgroundColour = new Color(backgroundColourInt);
        }

        //wait for all the images to be loaded
        int mediaIndex = 1;
        MediaTracker mt = new MediaTracker(this);
        mt.addImage(backGround, mediaIndex++);

        for (int i = 0; i < 7; i++) {
            mt.addImage(blockPics[i], mediaIndex++);
            mt.addImage(deadBlockPics[i], mediaIndex++);
        }
        mt.addImage(deadBlockPics[7], mediaIndex++);

        try {
            for (int i = 0; i < mediaIndex; i++) {
                mt.waitForID(i);
            }
        } catch (InterruptedException e) {
        }

        //set up key listener
        addKeyListener(new TetrisKeyAdapter());


    }

    /*
     * Start a new game with the appropriate settings
     */
    private void newGame(
    		int targetLines, int startHeight, int level) {
        well.updateChallengeSettings(targetLines, startHeight, level);
        controller.restart();
    }

    //draw a block in the required place
    private void drawBlock(int blockNum, int x, int y, Graphics gr) {
        if (blockNum == 0) {
            gr.setColor(Color.black);
            gr.fillRect(X_OFFSET + x*BLOCK_WIDTH,
                        Y_OFFSET + y*BLOCK_HEIGHT,
                        BLOCK_WIDTH,
                        BLOCK_HEIGHT);
            return;
        } else if (blockNum < 0) {
            gr.drawImage(deadBlockPics[-1 - blockNum], 
                         X_OFFSET + x*BLOCK_WIDTH,
                         Y_OFFSET + y*BLOCK_HEIGHT,
                         this);
        } else {
            gr.drawImage(blockPics[blockNum - 1],
                         X_OFFSET + x*BLOCK_WIDTH,
                         Y_OFFSET + y*BLOCK_HEIGHT,
                         this);
        }
    }

    //display the next block
    private void showNext (Graphics gr) {

        gr.setColor(Color.black);
        gr.fillRect(300, 227, BLOCK_WIDTH*4, BLOCK_HEIGHT*4);

        Block nextBlock = well.getNextBlock();
        int size = nextBlock.getSize();
        int xShift = 0;
        int yShift = 0;

        if (size == 4) {
            yShift = (int)(BLOCK_WIDTH / 2);
        }
        if (size == 3) {
            xShift = (int)(BLOCK_HEIGHT / 2);
        }
        if (size == 2) {
            xShift = BLOCK_WIDTH;
            yShift = BLOCK_HEIGHT;
        }
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                int squareNum = nextBlock.getElem(i, j);
                if (squareNum != 0) {
                    gr.drawImage(blockPics[squareNum - 1],
                                 300 + xShift + i*BLOCK_WIDTH, 
                                 227 + yShift + j*BLOCK_HEIGHT,
                                 this);
                }
            }
        }
    }

    //show the background image
    private void showBackground(Graphics g) {
        g.drawImage(backGround, 0, 0, this);
    }

    //show the number of lines made
    private void showLines(Graphics g, int lines) {

        g.setColor(Color.black);
        g.fillRect(311,141, 48, 28); 
        g.setColor(Color.red);
        g.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 12));
        g.drawString("" + lines, 315, 160);
    }

    private String formatTime(int secs) {
        if ((secs % 60) < 10) {
            return (int)(secs / 60) + ":0" + (secs % 60);
        } else {
            return (int)(secs / 60) + ":" + (secs % 60);
        }
    }


    public void showTime(Graphics g) {

        g.setColor(backgroundColour);
        g.fillRect(95, 410,70, 27); 
        g.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 20));
        g.setColor(Color.red);
        g.drawString(formatTime((int)(well.getRunningTimeInMillis() / 1000)),
                     95, 
                     430);
    }        

    /**
     * draw everything
     */
    public void paint(Graphics g) {

        /* paint the whole screen in the background colour */

        g.setColor(backgroundColour);
        g.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); 

        showBackground(g);

//        showOpHeight(g);
        showNext(g);

        int lines = (well.getTargetLines() == 0) ? well.getLines()
                                                 : well.getRemainingLines();
        showLines(g, lines);

        this.screenLines = lines;

        /* write "Time: " at the bottom */
        g.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 20));
        g.setColor(Color.red);
        g.drawString("Time: ", 25, 430);

        showTime(g);

        for (int i = 0; i < Well.WELL_WIDTH; i++ ) {
            for (int j = 0; j < Well.WELL_HEIGHT; j++ ) {
                int elem = well.getElem(i, j);
                drawBlock(elem, i, j, g);
                screenWell[i][j] = elem;
            }
        }
        
        // if the copyright message hasn't been shown yet, show it now
        if(!copyrightShown) {
            copyrightShown = true;
            (new AlertForm(frame, new String[]{"Netris", "Copyright 2004", "A. Todd"})).showForm();
        }
        
        showStatusMessage(g);
        requestFocus();
    }

    /**
     * Draw whatever has changed since last draw
     */
    public void update(Graphics g) {

        if (this.screenNextBlock != well.getNextBlock()) {
            showNext(g);
            this.screenNextBlock = well.getNextBlock();
        }

        int lines = (well.getTargetLines() == 0) ? well.getLines()
                                                 : well.getRemainingLines();
        if (this.screenLines != lines) {
            showLines(g, lines);
            this.screenLines = lines;
        }

        showTime(g);

        for (int i = 0; i < Well.WELL_WIDTH; i++ ) {
            for (int j = 0; j < Well.WELL_HEIGHT; j++ ) {
                int elem = well.getElem(i, j);
                if (elem != screenWell[i][j]) {
                    drawBlock(elem, i, j, g);
                    screenWell[i][j] = elem;
                }
            }
        }

        showStatusMessage(g);
        requestFocus();
    }

    private void showStatusMessage(Graphics g) {
        if (this.isNew) {
            g.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 30));
            drawOutlineString("Netris", 82, 170, g);
            g.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 20));
            drawOutlineString("Press '1'", 83, 220, g);
            drawOutlineString("to start", 89, 250, g);
        } else if (well.isGameOver()) {
            g.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 40));
            drawOutlineString("Game", 73, 203, g);
            drawOutlineString("Over", 82, 250, g);
        } else if (well.isComplete()) {
            g.setFont(new java.awt.Font("Arial", java.awt.Font.PLAIN, 40));
            drawOutlineString("Well", 73, 203, g);
            drawOutlineString("Done", 82, 250, g);
        }
    }

    private void drawOutlineString(String str, int x, int y, Graphics g) {
        drawOutlineString(str, x, y, g, Color.red, Color.black);
    }

//    private void drawOutlineString(String str, int x, int y, Graphics g,
//                                   Color strCol) {
//        drawOutlineString(str, x, y, g, strCol, Color.black);
//    }

    private void drawOutlineString(String str, int x, int y, Graphics g,
                                   Color strCol, Color outCol) {

        g.setColor(outCol);
        for (int i = x - 1; i < x + 2; i++) {
            for (int j = y - 1; j < y + 2; j++) {
                g.drawString(str, i, j);
            }
        }

        g.setColor(strCol);
        g.drawString(str, x, y);
    }

    /**
     * Method to get the user details on demand when a high score has been 
     * made
     * @return String[] element 0 is name, element 1 is where the user is from
     */
    public String[] getPlayerHighScoreDetails() { 
        hallOfFameForm.showForm();
        if (hallOfFameForm.wasCancelled()) {
            return null;
        } else {
            return new String[]{hallOfFameForm.getUserName(),
                                hallOfFameForm.getUserFrom()};
        }
    }
    


    /***********************
    * WellListener Methods *
    ***********************/

    /**
     * @param well
     */
    public void wellChanged(Well well) {
        repaint();     
    }
    
    /**
     * @param well
     * @param linesMade
     */
    public void madeLines(Well well, int linesMade) {
        for (int i = 0; i < linesMade; i++) {
            madeLineClip.play();
            try {
                Thread.sleep(50);
            } catch (InterruptedException ie) {
            }
        }
        /* wellChanged deals with repaint */     
    }

    /**
     * @param well
     */
    public void gameOver(Well well) {

        repaint();     
    }
    
    /**
     * @param well
     */
    public void wellReset(Well well) {

        /* do a full redraw */
        paint(this.getGraphics());
    }
    
    /**
     * @param well
     */
    public void blockLanded(Well well) {
        landBlockClip.play();

    }
    
    /**
     * @param well
     */
    public void complete(Well well) {
        repaint();     
    }
    

//XXXXXXXXXXXX TetrisKeyAdapter XXXXXXXXXXXXXXX//

    class TetrisKeyAdapter extends KeyAdapter {

        public void keyPressed(java.awt.event.KeyEvent e) {
            
            char thisKey = e.getKeyChar();
            switch (thisKey) {
            case 'z' :
                //controller.left();
                ToddtrisApplet.this.controller.startLeft();
                break;
            case 'x' :
                //controller.right();
                ToddtrisApplet.this.controller.startRight();
                break;
            case 'k' :
            case 'l' :
            case ',' :
            case 'i' :
            case '8' :
            case '9' :
            case 'o' :
            case '.' :
                if (controller.rotate()) {
                    rotateClip.play();
                }
                break;
            case 'm' :
            case 'j' :
            case 'u' :
            case '7' :
                if (controller.antirotate()) {
                    rotateClip.play();
                }
                break;
            case ' ' :
                //controller.down();
                ToddtrisApplet.this.controller.startDown();
                break;
            case '1' :
                startGameForm.showForm();
                if (startGameForm.wasCancelled()) {
                    // user cancelled the pop-up
                    break;
                }
                break;
            }
        }

        public void keyReleased(java.awt.event.KeyEvent e) {

            char thisKey = e.getKeyChar();

            /* check for the particular key presses */
            switch (thisKey) {
            case 'z':
                ToddtrisApplet.this.controller.stopLeft();
                break;
            case 'x':
                ToddtrisApplet.this.controller.stopRight();
                break;
            case ' ':
                ToddtrisApplet.this.controller.stopDown();
                break;
            }     
        }

    }

//XXXXXXXXXXXX ToddtrisForm XXXXXXXXXXXXXXXX//

    private abstract class ToddtrisForm extends Dialog {

        /* did the user click the "x" in the corner of the pop-up? */
        private boolean cancelled = false;

        private KeyAdapter enterAdapter;
    
        ToddtrisForm(Frame frame) {
            super(frame, "Netris", true);  

            /* add a window adapter to close the window when appropriate */
            this.addWindowListener(
                new WindowAdapter() {
                    public void windowClosing(WindowEvent e) {
                        ToddtrisForm.this.cancel();
                    }
                }
            );

            enterAdapter = new KeyAdapter() {
                public void keyPressed(KeyEvent ke) {
                    if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
                        ToddtrisForm.this.ok();
                    }
                }
            };

            this.addKeyListener(enterAdapter);
        }

        void addEnterKeyListenerToComponent(Component component) {
            final ToddtrisForm form = this;
            KeyAdapter enterAdapter = new KeyAdapter() {
                public void keyPressed(KeyEvent ke) {
                    if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
                        form.ok();
                    }
                }
            };
            component.addKeyListener(enterAdapter);
        }

        void addOkButtonToContainer(Container container) {
            Button okBtn = new Button("OK");
            final ToddtrisForm form = this;
            okBtn.addActionListener(
                new ActionListener() {
                    public void actionPerformed(ActionEvent ae) {
                        form.ok();
                    }
                }
            );
            container.add(okBtn);
            this.addEnterKeyListenerToComponent(okBtn);
        }

        void addCancelButtonToContainer(Container container) {
            Button cancelBtn = new Button("Cancel");
            final ToddtrisForm form = this;
            cancelBtn.addActionListener(
                new ActionListener() {
                    public void actionPerformed(ActionEvent ae) {
                        form.cancel();
                    }
                }
            );
            container.add(cancelBtn);
        }

        void showForm() {
            this.cancelled = false;
            
            /*
             * Get the dimensions of the applet and the form so that we
             * can centre the form
             */
            Dimension formDim = this.getSize();
            Dimension surroundDim = (ToddtrisApplet.this).getSize();

            /* 
             * if the form is narrower than well, then centre on the well 
             * instead 
             */
            if(formDim.width < ToddtrisApplet.WELL_WIDTH) {
                surroundDim.width = ToddtrisApplet.WELL_WIDTH;
            }
            
            Point formPoint = (ToddtrisApplet.this).getLocationOnScreen();
            formPoint.translate((int)((surroundDim.width
                                       - formDim.width) / 2),
                               (int)((surroundDim.height
                                       - formDim.height) / 2));
            this.setLocation(formPoint);

            /* show the form */
            setVisible(true);
        }

        void cancel() {
            this.cancelled = true;
            this.setVisible(false);
            ToddtrisApplet.this.requestFocus();
        }

        void ok() {
            this.cancelled = false;
            this.setVisible(false);
            ToddtrisApplet.this.requestFocus();
        }

        boolean wasCancelled() {
            return this.cancelled;
        }
    }

//XXXXXXXXXXXX AlertForm XXXXXXXXXXXXXXXXX//
    
    private class AlertForm extends ToddtrisForm {

        AlertForm(Frame frame, String oneLineMessage) {
            this(frame, new String[] {
                oneLineMessage
            });
        }

        AlertForm(Frame frame, String messageLines[]) {
            super(frame);
            setLayout(new GridLayout(0, 1));
            for(int i1 = 0; i1 < messageLines.length; i1++) {
                Panel messagePanel = new Panel();
                messagePanel.add(new Label(messageLines[i1]));
                add(messagePanel);
            }

            Panel buttonPanel = new Panel();
            addOkButtonToContainer(buttonPanel);
            add(buttonPanel);
            pack();
        }
    }
//XXXXXXXXXXXX HallOfFameForm XXXXXXXXXXXXXXX//
    
    private class HallOfFameForm extends ToddtrisForm {

        private TextField nameField = new TextField(20);
        private TextField fromField = new TextField(20);
    
        HallOfFameForm(Frame frame) {
            super(frame);                  
            this.setLayout(new GridLayout(0, 1));

            Panel msg1Panel = new Panel();
            msg1Panel.add(new Label(
                    "Congratulations you've made a high score."));
            this.add(msg1Panel);

            Panel msg2Panel = new Panel();
            msg2Panel.add(new Label(
                    "Enter you're details to join the Netris Hall of Fame!"));
            this.add(msg2Panel);
            
            Panel namePanel = new Panel();
            namePanel.add(new Label("Name:"));
            namePanel.add(nameField);
            this.add(namePanel);
            
            Panel fromPanel = new Panel();
            fromPanel.add(new Label("From:"));
            fromPanel.add(fromField);
            this.add(fromPanel);
    
            Panel btnPanel = new Panel();
            this.addOkButtonToContainer(btnPanel);
            this.addCancelButtonToContainer(btnPanel);
            this.add(btnPanel);


            this.addEnterKeyListenerToComponent(nameField);
            this.addEnterKeyListenerToComponent(fromField);

            this.pack();
        }

        void showForm() {
            /* focus the name field */
            nameField.requestFocus();
            super.showForm();
        }

        String getUserName() {
            return nameField.getText();
        }

        String getUserFrom() {
            return fromField.getText();
        }
  }

//XXXXXXXXXXXX StartGameForm XXXXXXXXXXXXXXX//
    
    private class StartGameForm extends ToddtrisForm {

        private CheckboxGroup cbg = new CheckboxGroup();
        private final Checkbox marathonCB 
                = new Checkbox("Marathon", cbg, true);
        private final Checkbox timeTrialCB 
                = new Checkbox("Time Trial", cbg, false);

        StartGameForm(Frame frame) {
            super(frame);  

            this.setLayout(new GridLayout(0, 1));

            add(new Label("Select Game Type."));

            this.add(marathonCB);
            this.add(timeTrialCB);
            Panel btnPanel = new Panel();
            this.addOkButtonToContainer(btnPanel);
            this.addCancelButtonToContainer(btnPanel);
            this.add(btnPanel);
            this.addEnterKeyListenerToComponent(marathonCB);
            this.addEnterKeyListenerToComponent(timeTrialCB);
            
            /* 
             * create listeners to respond to up/down arrows when selecting 
             * game type
             */
            marathonCB.addKeyListener(
                    new KeyAdapter() {
                        public void keyPressed(KeyEvent ke) {
                            if (ke.getKeyCode() == KeyEvent.VK_DOWN) {
                                timeTrialCB.requestFocus();
                                timeTrialCB.setState(true);
                            }
                        }
                    });

            timeTrialCB.addKeyListener(
                    new KeyAdapter() {
                        public void keyPressed(KeyEvent ke) {
                            if (ke.getKeyCode() == KeyEvent.VK_UP) {
                                marathonCB.requestFocus();
                                marathonCB.setState(true);
                            }
                        }
                    });

            
            this.pack();
        }

        void showForm() {
            /* focus the marathon checkbox */
            marathonCB.requestFocus();
            super.showForm();
        }

        String getGameType() {
            String type = "";
            if (cbg.getSelectedCheckbox() == marathonCB) {
                type = "M";
            } else if (cbg.getSelectedCheckbox() == timeTrialCB) {
                type = "TT";
            }
            return type;
        }

        /*
         * start the appropriate game when the user clicks ok
         */
        void ok() {
            super.ok();
            if(wasCancelled()) {
            	// user cancelled the pop-up
                return;
            }
            // don't display welcome message any more
            isNew = false;
            String type = getGameType();
            int targetLines = type.equals("TT") ? 40 : 0;
            int startHeight = 0;
            int level = 0;
            ToddtrisApplet.this.newGame(
            		targetLines, startHeight, level);
        }

    }
}
